/**
 * 
 */
package neptune.manager.storage;

import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import neptune.manager.ExperimentException;
import neptune.manager.IsUser;
import neptune.manager.User;
import neptune.manager.model.experiment.Experiment;
import neptune.manager.model.experiment.ExperimentState;
import neptune.manager.model.topology.IPAddress;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * @author Roberto Bifulco [RobertoBifulco.it] [info@robertoBifulco.it]
 * @author Luigi Lorenzo Arpino [luigiarpino@yahoo.it]
 */
public class XMLExperimentsRepository implements ExperimentsRepository {

	private static final int ID = 0;
	private static final int ADMIN = 1;
	private static final int STATE = 2;
	private static final int CREATION_DATE = 3;
	private String repositoryPath;

	/**
	 * 
	 * 
	 * @param repositoryPath
	 *            the path to the directory used to store experiment descriptors
	 */
	public XMLExperimentsRepository(String repositoryPath) {
		super();
		if (!repositoryPath.endsWith(File.separator))
			repositoryPath += File.separator;
		this.repositoryPath = repositoryPath;
		File dirs = new File(repositoryPath);
		dirs.mkdirs();
	}

	/**
	 * Crea un oggetto Experiment partendo dalla sua descrizione XML. La
	 * topologia dell'esperimento non e' caricata in questa sede ma deve essere
	 * caricata ed impostata in un secondo momento adoperando un TopologyStorage
	 * 
	 * @param expName
	 * @return
	 * @throws ExperimentRepositoryException
	 */
	public synchronized Experiment readExperimentFile(String expName)
			throws ExperimentRepositoryException {
		File xmlFile = new File(repositoryPath + expName + ".xml");

		DocumentBuilderFactory docFactory = DocumentBuilderFactory
				.newInstance();
		docFactory.setValidating(false);

		try {
			Document doc = docFactory.newDocumentBuilder().parse(xmlFile);

			Element expEl = doc.getDocumentElement();
			String expId = expEl.getAttribute("id");

			Element adminEl = (Element) doc.getElementsByTagName(
					"administrator").item(0);
			String admin = adminEl.getAttribute("id");

			Experiment exp = new Experiment(admin, expId);

			// STATE
			Element stateEl = (Element) doc.getElementsByTagName("state").item(
					0);
			exp.getState().setValue(stateEl.getAttribute("value"));
			Element startTime = (Element) stateEl.getElementsByTagName(
					"startTimeStamp").item(0);
			exp.getState().setStartTimestamp(
					Long.parseLong(startTime.getAttribute("value")));
			Element stopTime = (Element) stateEl.getElementsByTagName(
					"stopTimeStamp").item(0);
			exp.getState().setStopTimestamp(
					Long.parseLong(stopTime.getAttribute("value")));
			Element creationTime = (Element) stateEl.getElementsByTagName(
					"creationTimeStamp").item(0);
			exp.getState().setCreationTimestamp(
					Long.parseLong(creationTime.getAttribute("value")));
			Element executionTime = (Element) stateEl.getElementsByTagName(
					"totalExecutionTime").item(0);
			exp.getState().setTotalExecutionTime(
					Long.parseLong(executionTime.getAttribute("value")));

			// SUBNET
			if (doc.getElementsByTagName("subnet").getLength() != 0) {
				Element subnetEl = (Element) doc.getElementsByTagName("subnet")
						.item(0);
				IPAddress subnet = new IPAddress(4, subnetEl
						.getAttribute("address"), subnetEl
						.getAttribute("netmask"));
				exp.setAssignedSubnet(subnet);
			}

			// USERS
			Element users = (Element) doc.getElementsByTagName("users").item(0);
			NodeList userEls = users.getChildNodes();
			for (int i = 0; i < userEls.getLength(); i++) {
				if (userEls.item(i) instanceof Element) {
					Element user = (Element) userEls.item(i);
					exp.addUser(new User(user.getAttribute("id")));
				}
			}

			return exp;
		} catch (Exception e) {
			throw new ExperimentRepositoryException(e);
		}
	}

	/**
	 * Serializza lo stato dell'esperimento passato come argomento in un
	 * documento xml che viene creato nella directory indicata come repository.
	 * Da notare che questa operazione non effettua la serializzazione della
	 * topologia, che deve essere fatta tramite un TopologyStorage.
	 * 
	 * @param exp
	 * @throws ExperimentRepositoryException
	 */
	public synchronized void writeExperimentFile(Experiment exp)
			throws ExperimentRepositoryException {
		File xmlFile = new File(repositoryPath + exp.getExperimentId() + ".xml");

		DocumentBuilderFactory docFactory = DocumentBuilderFactory
				.newInstance();
		docFactory.setValidating(false);

		try {
			Document document = docFactory.newDocumentBuilder().newDocument();
			document.setXmlVersion("1.0");

			Element expEl = document.createElement("experiment");
			document.appendChild(expEl);

			expEl.setAttribute("id", exp.getExperimentId());

			Element admin = document.createElement("administrator");
			admin.setAttribute("id", exp.getAdministrator());
			expEl.appendChild(admin);

			// STATE
			Element state = document.createElement("state");
			state.setAttribute("value", exp.getState().getValue());
			expEl.appendChild(state);

			Element startTime = document.createElement("startTimeStamp");
			startTime.setAttribute("value", Long.toString(exp.getState()
					.getStartTimestamp()));
			state.appendChild(startTime);

			Element stopTime = document.createElement("stopTimeStamp");
			stopTime.setAttribute("value", Long.toString(exp.getState()
					.getStopTimestamp()));
			state.appendChild(stopTime);

			Element creationTime = document.createElement("creationTimeStamp");
			creationTime.setAttribute("value", Long.toString(exp.getState()
					.getCreationTimestamp()));
			state.appendChild(creationTime);

			Element execTime = document.createElement("totalExecutionTime");
			execTime.setAttribute("value", Long.toString(exp.getState()
					.getTotalExecutionTime()));
			state.appendChild(execTime);

			// SUBNET
			if (exp.getAssignedSubnet() != null) {
				Element subnetEl = document.createElement("subnet");
				subnetEl.setAttribute("netmask", exp.getAssignedSubnet()
						.getSubnetMask());
				subnetEl.setAttribute("address", exp.getAssignedSubnet()
						.getAddress());
				expEl.appendChild(subnetEl);
			}

			// USERS
			Element users = document.createElement("users");
			expEl.appendChild(users);

			for (IsUser user : exp.getUsers()) {
				Element userEl = document.createElement("user");
				userEl.setAttribute("id", user.getUsername());
				users.appendChild(userEl);
			}

			// ///////////////
			// Output the XML

			// set up a transformer
			TransformerFactory transfac = TransformerFactory.newInstance();
			Transformer trans = transfac.newTransformer();
			trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
			trans.setOutputProperty(OutputKeys.INDENT, "yes");
			trans.setOutputProperty(OutputKeys.STANDALONE, "yes");
			trans.setOutputProperty(
					"{http://xml.apache.org/xslt}indent-amount", "2");

			// create string from xml tree
			StringWriter sw = new StringWriter();
			StreamResult result = new StreamResult(sw);
			DOMSource source = new DOMSource(document);
			trans.transform(source, result);
			String xmlString = sw.toString();

			xmlFile.createNewFile();
			FileWriter writer = new FileWriter(xmlFile);
			writer.write(xmlString);
			writer.flush();

		} catch (Exception e) {
			throw new ExperimentRepositoryException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#addExperimentToRepository
	 * (neptune.manager.model.experiment.Experiment)
	 */
	@Override
	public void addExperimentToRepository(Experiment experiment)
			throws ExperimentException, ExperimentRepositoryException {
		writeExperimentFile(experiment);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#addExperimentUser(java.
	 * lang.String, java.lang.String, java.lang.String)
	 */
	@Override
	public void addExperimentUser(String exp, String usr, String role)
			throws ExperimentRepositoryException
	{
		//TODO LUIGI
		Experiment expObj = readExperimentFile(exp);
		if (role.equals(Experiment.Roles.NORMAL_USER))
		{
			expObj.addUser(new User(usr));
		}
		writeExperimentFile(expObj);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#createExperiment(java.lang
	 * .String, java.lang.String, long)
	 */
	@Override
	public void createExperiment(String expName, String expAdmin,
			long creationTimestamp) throws ExperimentRepositoryException {
		
		if(getExperimentByName(expName) != null)
			throw new ExperimentRepositoryException("Experiment '" + expName + "' already exists");
		
		
		Experiment exp = new Experiment(expAdmin, expName);
		exp.getState().setCreationTimestamp(creationTimestamp);

		writeExperimentFile(exp);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#deleteExperiment(java.lang
	 * .String)
	 */
	@Override
	public boolean deleteExperiment(String expName)
			throws ExperimentRepositoryException
	{
		boolean isRemoved=false;
		File expFile = new File(repositoryPath + expName + ".xml");
		if (expFile != null)
		{
			expFile.delete();
			isRemoved=true;
		}
		return isRemoved;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#deleteExperimentUser(java
	 * .lang.String, java.lang.String)
	 */
	@Override
	public void deleteExperimentUser(String exp, String usr)
			throws ExperimentRepositoryException {
		Experiment expObj = readExperimentFile(exp);
		expObj.removeUser(usr);
		writeExperimentFile(expObj);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see neptune.manager.storage.ExperimentsRepository#getActiveExperiments()
	 */
	@Override
	public String[] getActiveExperiments() throws ExperimentRepositoryException {
		ArrayList<String> active = new ArrayList<String>(0);
		String[] exps = getExperiments();
		for (String expId : exps) {
			Experiment expObj = readExperimentFile(expId);
			if (expObj.getState().getValue().equals(
					ExperimentState.StatesID.RUNNING_LABEL))
				active.add(expId);
		}

		return active.toArray(new String[] {});
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#getExperimentAdmin(java
	 * .lang.String)
	 */
	@Override
	public String getExperimentAdmin(String expName)
			throws ExperimentRepositoryException {
		Experiment exp = readExperimentFile(expName);
		return exp.getAdministrator();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#getExperimentByName(java
	 * .lang.String)
	 */
	@Override
	public Experiment getExperimentByName(String name)
			throws ExperimentRepositoryException {
		try {
			return readExperimentFile(name);
		} catch (Exception e) {
			return null;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#getExperimentNumberForUser
	 * (java.lang.String, boolean)
	 */
	@Override
	public int getExperimentNumberForUser(String username, boolean isSystemAdmin)
			throws ExperimentRepositoryException {
		if (isSystemAdmin)
			return getExperiments().length;

		String[] exps = getExperiments();
		int expNumber = 0;
		for (String expId : exps) {
			Experiment exp = readExperimentFile(expId);
			if (exp.getAdministrator().equals(username))
				expNumber++;
			else {
				for (IsUser user : exp.getUsers()) {
					if (user.getUsername().equals(username)) {
						expNumber++;
						break;
					}
				}
			}
		}
		return expNumber;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#getExperimentStartTime()
	 */
	@Override
	public long getExperimentStartTime(String expId)
			throws ExperimentRepositoryException {
		Experiment exp = readExperimentFile(expId);
		return exp.getState().getStartTimestamp();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#getExperimentState(java
	 * .lang.String)
	 */
	@Override
	public String getExperimentState(String expName)
			throws ExperimentRepositoryException {
		Experiment exp = readExperimentFile(expName);
		return exp.getState().getValue();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#getExperimentStopTime()
	 */
	@Override
	public long getExperimentStopTime(String expId)
			throws ExperimentRepositoryException {
		Experiment exp = readExperimentFile(expId);
		return exp.getState().getStopTimestamp();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#getExperimentTable(int,
	 * int, int, boolean, java.lang.String, boolean)
	 */
	@Override
	public String[][] getExperimentTable(int begin, int end, int sortId,
			boolean ascending, String username, boolean isAdmin)
			throws ExperimentRepositoryException {
		try {
			String[] epxsId = getExperiments();

			final int sortCell;
			switch (sortId) {
			case 0:
				sortCell = ID;
				break;
			case 1:
				sortCell = ADMIN;
				break;
			case 2:
				sortCell = STATE;
				break;
			default:
				sortCell = CREATION_DATE;
				break;
			}

			final int sign = ascending ? 1 : -1;

			ArrayList<Experiment> exps = new ArrayList<Experiment>(0);
			for (String expId : epxsId) {
				Experiment exp = readExperimentFile(expId);
				if (isAdmin)
					exps.add(exp);
				else if (exp.getAdministrator().equals(username))
					exps.add(exp);
				else {
					for (IsUser user : exp.getUsers()) {
						if (user.getUsername().equals(username)) {
							exps.add(exp);
							break;
						}
					}
				}
			}

			Experiment[] expsArray = exps.toArray(new Experiment[] {});
			Arrays.sort(expsArray, new Comparator<Experiment>() {
				public int compare(Experiment o1, Experiment o2) {
					switch (sortCell) {
					case ID:
						return sign
								* o1.getExperimentId().compareTo(
										o2.getExperimentId());
					case ADMIN:
						return sign
								* o1.getAdministrator().compareTo(
										o2.getAdministrator());
					case STATE:
						return sign
								* o1.getState().getValue().compareTo(
										o2.getState().getValue());
					case CREATION_DATE:
						long v1 = o1.getState().getCreationTimestamp();
						long v2 = o2.getState().getCreationTimestamp();
						return sign * (v1 < v2 ? -1 : (v1 == v2 ? 0 : 1));
					default:
						return sign
								* o1.getExperimentId().compareTo(
										o2.getExperimentId());
					}
				}
			});

			if (begin > exps.size())
				begin = exps.size();
			if (end > exps.size())
				end = exps.size();
			if (begin > end)
				begin = end;

			String[][] subArray = new String[end - begin][];
			for (int i = 0; i < subArray.length; i++) 
			{
				Date expDate=new Date(expsArray[i + begin].getState()
						.getCreationTimestamp());
				subArray[i] = new String[] 
						                    {
								expsArray[i + begin].getExperimentId(),
								expsArray[i + begin].getAdministrator(),
								expsArray[i + begin].getState().getValue(),
								expDate.toString() };
				/*
				subArray[i] = new String[] 
				                    {
						expsArray[i + begin].getExperimentId(),
						expsArray[i + begin].getAdministrator(),
						expsArray[i + begin].getState().getValue(),
						Long.toString(expsArray[i + begin].getState()
								.getCreationTimestamp()) };
				*/
			}

			return subArray;
		} catch (Exception e) {
			throw new ExperimentRepositoryException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#getExperimentUsers(java
	 * .lang.String)
	 */
	@Override
	public String[][] getExperimentUsers(String exp)
			throws ExperimentRepositoryException {
		Experiment expObj = readExperimentFile(exp);

		String[][] users = new String[expObj.getUsers().length
				+ expObj.getPrivilegedUsers().length][];
		IsUser[] usersArray = expObj.getUsers();
		for (int i = 0; i < usersArray.length; i++) {
			users[i] = new String[] { usersArray[i].getUsername(),
					Experiment.Roles.NORMAL_USER };
		}
		IsUser[] superUsersArray = expObj.getPrivilegedUsers();
		for (int i = usersArray.length; i < users.length; i++) {
			users[i] = new String[] {
					superUsersArray[i - usersArray.length].getUsername(),
					Experiment.Roles.SUPER_USER };
		}
		return users;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see neptune.manager.storage.ExperimentsRepository#getExperiments()
	 */
	@Override
	public String[] getExperiments() throws ExperimentRepositoryException {

		File expDir = new File(repositoryPath);

		String[] list = expDir.list(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return name.matches(".*\\.xml"); // considera solo i file .xml
			}
		});

		for (int i = 0; i < list.length; i++) {
			list[i] = list[i].split("\\.")[0];
		}

		return list;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#removeExperimentFromRepository
	 * (neptune.manager.model.experiment.Experiment)
	 */
	@Override
	public void deleteExperiment(Experiment experiment)
			throws ExperimentRepositoryException {
		deleteExperiment(experiment.getExperimentId());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#setExperimentStartTime(
	 * long)
	 */
	@Override
	public void setExperimentStartTime(String expId, long timestamp)
			throws ExperimentRepositoryException {
		Experiment exp = readExperimentFile(expId);
		exp.getState().setStartTimestamp(timestamp);
		writeExperimentFile(exp);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#setExperimentState(java
	 * .lang.String, java.lang.String)
	 */
	@Override
	public void setExperimentState(String expName, String expState)
			throws ExperimentRepositoryException {
		Experiment exp = readExperimentFile(expName);
		exp.getState().setValue(expState);
		writeExperimentFile(exp);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#setExperimentStopTime(long)
	 */
	@Override
	public void setExperimentStopTime(String expId, long timestamp)
			throws ExperimentRepositoryException {
		Experiment exp = readExperimentFile(expId);
		exp.getState().setStopTimestamp(timestamp);
		writeExperimentFile(exp);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ExperimentsRepository#updateExperiment(neptune
	 * .manager.model.experiment.Experiment)
	 */
	@Override
	public void updateExperiment(Experiment experiment)
			throws ExperimentRepositoryException {
		writeExperimentFile(experiment);
	}

}
